/***************************************************************************
 * Copyright (C) 2005 Global Biodiversity Information Facility Secretariat.
 * All Rights Reserved.
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 ***************************************************************************/
package org.gbif.portal.web.controller.registration;

import org.gbif.portal.dto.KeyValueDTO;
import org.gbif.portal.dto.resources.DataProviderDTO;
import org.gbif.portal.dto.resources.DataResourceDTO;
import org.gbif.portal.registration.ErrorMessageKeys;
import org.gbif.portal.registration.LDAPUtils;
import org.gbif.portal.registration.ResourceExtractionUtils;
import org.gbif.portal.registration.UDDIUtils;
import org.gbif.portal.registration.model.ProviderDetail;
import org.gbif.portal.registration.model.ResourceDetail;
import org.gbif.portal.registration.model.UserLogin;
import org.gbif.portal.service.DataResourceManager;
import org.gbif.portal.service.GeospatialManager;
import org.gbif.portal.service.TaxonomyManager;
import org.gbif.portal.util.mhf.message.Message;
import org.gbif.portal.util.propertystore.PropertyStore;
import org.gbif.portal.util.workflow.ProcessContext;
import org.gbif.portal.util.workflow.Processor;
import org.gbif.portal.web.util.DateUtil;
import org.gbif.portal.web.util.PasswordUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.naming.NamingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.context.MessageSource;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.view.RedirectView;
import org.uddi4j.UDDIException;
import org.uddi4j.transport.TransportException;

/**
 * The dispatcher controller for all provider updating activities for the
 * wizard to create or update a provider.
 * The user is capable of creating and editing data providers in uddi
 * Forms are pre-populated from the LDAP configuration,
 * 
 * @author trobertson
 * @author dmartin
 */
public class RegistrationController extends MultiActionController {

  /** The request constants */
  public static final String REQUEST_BUSINESS_UDDI_KEY = "businessKey";
  public static final String REQUEST_CONTACT_TYPES = "contactTypes";
  public static final String REQUEST_PROVIDER_DETAIL = "providerDetail";
  public static final String REQUEST_COUNTRIES = "countries";
  public static final String REQUEST_RESOURCE_NETWORKS = "resourceNetworks";
  public static final String REQUEST_BASES_OF_RECORD = "basesOfRecord";
  public static final String REQUEST_RESOURCE_URL = "resourceUrl";
  public static final String REQUEST_RESOURCES = "resources";
  public static final String REQUEST_RESOURCE = "resource";
  public static final String REQUEST_SELECTED_CONCEPT = "selectedConcept";
  public static final String REQUEST_DATA_PROVIDER = "dataProvider";
  public static final String REQUEST_DATA_RESOURCE = "dataResource";
  public static final String REQUEST_CONCEPTS = "concepts";
  public static final String REQUEST_RESOURCE_TYPES = "resourceTypes";

  /** The name of the admin role */
  protected String adminRole = "Portal Admins";

  /** MailSender */
  protected MailSender mailSender;

  protected SimpleMailMessage userTemplateMessage;

  protected String usernamePostfix = "";

  /** The LDAP URL */
  protected String ldapUrl;

  /** Utils */
  protected UDDIUtils uddiUtils;
  protected LDAPUtils ldapUtils;
  protected ResourceExtractionUtils resourceExtractionUtils;

  /** The property store */
  protected PropertyStore propertyStore;

  /** Managers */
  protected GeospatialManager geospatialManager;
  protected DataResourceManager dataResourceManager;
  protected TaxonomyManager taxonomyManager;

  /** the basis of records */
  protected List<String> basisOfRecordTypes;

  /** the resource types supported */
  protected List<String> resourceTypes;

  /** resource 2 namespace mappings */
  protected Map<String, String> resourceType2namespaceList;

  /** Minimum username length */
  protected int minimumUsernameLength = 6;

  /** Minimum password length */
  protected int minimumPasswordLength = 6;

  public PasswordUtils passwordUtils = new PasswordUtils();

  protected String adminEmail = "portal@gbif.org";

  /** Message source for i18n */
  protected MessageSource messageSource;

  /**
   * Ajax entry point that adds the resource to provider
   */
  @SuppressWarnings("unchecked")
  public ModelAndView addDataResource(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logger.info("Adding a resource...");
    debugRequestParams(request);

    ResourceDetail resourceDetail = new ResourceDetail();
    ServletRequestDataBinder binder = createBinder(request, resourceDetail);
    binder.bind(request);
    BindingResult result = binder.getBindingResult();
    validateDataResource(resourceDetail, result);

    logger.info("Adding the resource: " + resourceDetail.getName());
    uddiUtils.updateResource(resourceDetail, request.getParameter("businessKey"));
    Map<String, Object> data = new HashMap<String, Object>();
    data.put(REQUEST_RESOURCE, resourceDetail);
    data.putAll(referenceDataForResourceList(request));
    return null;
  }

  /**
   * Setup page for a new data provider. When registering a new contact, these some details are pre-populated from LDAP
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView addRegistrationLogins(HttpServletRequest request, HttpServletResponse response) throws Exception {

    String user = request.getParameter("user");
    String email = request.getParameter("email");

    // retrieve the user
    UserLogin userLogin = null;
    if (StringUtils.isNotEmpty(user)) {
      userLogin = ldapUtils.getUserLogin(user);
    }

    // if not found, try with email
    if (userLogin == null && StringUtils.isNotEmpty(email)) {
      List<UserLogin> userLogins = ldapUtils.getUsernamePasswordForEmail(email);
      if (userLogins.size() > 1 || userLogins.isEmpty()) {
        ModelAndView mav = new ModelAndView("registrationUserUpdate");
        if (userLogins.isEmpty()) {
          mav.addObject("userNotFound", true);
          mav.addObject("user", user);
          mav.addObject("email", email);
        } else {
          mav.addObject("userLogins", userLogins);
        }
        return mav;
      } else {
        userLogin = userLogins.get(0);
      }
    }

    // if not found at all
    if (userLogin == null) {
      ModelAndView mav = new ModelAndView("registrationUserUpdate");
      mav.addObject("userNotFound", true);
      mav.addObject("user", user);
      mav.addObject("email", email);
      return mav;
    }

    String username = getSystemUserName(userLogin.getUsername());

    List<KeyValueDTO> providerList = uddiUtils.getProviderListAsKeyValues();
    List<String> businessKeys = uddiUtils.getAssociatedBusinessKeys(username);
    List<KeyValueDTO> providerRegistrationLogins = new ArrayList<KeyValueDTO>();
    for (KeyValueDTO providerKV : providerList) {
      if (businessKeys.contains(providerKV.getKey())) {
        providerRegistrationLogins.add(providerKV);
      }
    }
// providerList.removeAll(providerRegistrationLogins);
    ModelAndView mav = new ModelAndView("registrationProviderList");
    mav.addObject("providerList", providerList);
    mav.addObject("providerRegistrationLogins", providerRegistrationLogins);
    mav.addObject("userLogin", userLogin);
    mav.addObject("updateAction", "updateRegistrationLogins");
    return mav;
  }

  /**
   * Updates details in UDDI for this dataset.
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView completeManualRegistration(HttpServletRequest request, HttpServletResponse response)
    throws Exception {

    // bind to object
    ResourceDetail resource = new ResourceDetail();
    ServletRequestDataBinder binder = createBinder(request, resource);
    initBinder(request, binder);
    binder.bind(request);
    BindingResult result = binder.getBindingResult();
    validateDataResource(resource, result);
    logger.info("Adding the resource: " + resource.getName());

    if (result.hasErrors()) {
      logger.info("Errors have occurred: " + result);
      Map<String, Object> data = new HashMap<String, Object>();
      data.put(REQUEST_RESOURCE, resource);
      data.putAll(referenceDataForResourceList(request));
      data.put(BindingResult.MODEL_KEY_PREFIX + RegistrationController.REQUEST_RESOURCE, result);
      data.put("editableEndpoint", true);
      // put the errors in the request
      return new ModelAndView("registrationResourceManual", data);
    } else {
      String businessKey = request.getParameter("businessKey");
      uddiUtils.updateResource(resource, businessKey);
      return new ModelAndView(new RedirectView(request.getContextPath() + "/register/showDataResources?businessKey="
        + businessKey));
    }
  }

  /**
   * Issues a metadata request to the provider
   */
  @SuppressWarnings("unchecked")
  protected List<ResourceDetail> contactProviderForMetadata(String url) {
    logger.debug("Contacting provider for metadata");

    if (resourceType2namespaceList != null) {
      for (String resourceType : resourceType2namespaceList.keySet()) {

        // retrieve the namespace
        String namespace = resourceType2namespaceList.get(resourceType);
        try {
          // this is geared to DiGIR only
          Processor workflow =
            (Processor) propertyStore.getProperty(namespace, "REGISTRATION.METADATA.WORKFLOW", Processor.class);
          Map<String, Object> seedData = new HashMap<String, Object>();
          seedData.put("url", url);
          List<String> namespaces = new LinkedList<String>();
          namespaces.add(namespace);
          seedData.put("psNamespaces", namespaces);
          ProcessContext context = workflow.doActivities(seedData);

          Message message = (Message) context.get("responseMessage", Message.class, true);
          List<ResourceDetail> resources = resourceExtractionUtils.getResourcesFromMetadata(namespaces, message);
          if (resources != null && !resources.isEmpty()) {
            for (ResourceDetail resourceDetail : resources)
              resourceDetail.setResourceType(resourceType);
            return resources;
          }
        } catch (Exception e) {
          logger.error(e.getMessage(), e);
        }
      }
    }
    return new LinkedList<ResourceDetail>();
  }

  /**
   * Debug request parameters.
   * 
   * @param request
   */
  @SuppressWarnings("unchecked")
  private void debugRequestParams(HttpServletRequest request) {
    Map<String, Object> params = request.getParameterMap();
    for (String key : params.keySet()) {
      Object param = params.get(key);
      if (param instanceof String[]) {
        for (int i = 0; i < ((String[]) param).length; i++) {
          logger.info(key + "[" + i + "]: " + ((String[]) param)[i]);
        }
      } else {
        logger.info(key + ": " + 1);
      }
    }
  }

  /**
   * Enables a user to find a provider and request access to provider details.
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView findDataProvider(HttpServletRequest request, HttpServletResponse response) throws Exception {
    List<KeyValueDTO> providerList = uddiUtils.getProviderListAsKeyValues();
    List<String> businessKeys = uddiUtils.getAssociatedBusinessKeys(request.getRemoteUser());
    List<KeyValueDTO> providerRegistrationLogins = new ArrayList<KeyValueDTO>();

    List<KeyValueDTO> toRemove = new ArrayList<KeyValueDTO>();
    for (KeyValueDTO providerKV : providerList) {
      if (businessKeys.contains(providerKV.getKey())) {
        providerRegistrationLogins.add(providerKV);
        toRemove.add(providerKV);
      }
    }

    // remove the ones already accessible
    providerList.removeAll(toRemove);

    // view this of providers
    ModelAndView mav = new ModelAndView("registrationProviderList");
    mav.addObject("providerList", providerList);
    mav.addObject("providerRegistrationLogins", providerRegistrationLogins);

    // if user is admin, not need to send requests
    if (request.isUserInRole(adminRole)) {
      mav.addObject("updateAction", "updateRegistrationLogins");
    } else {
      mav.addObject("updateAction", "sendRegistrationLoginsRequest");
    }
    return mav;
  }

// public static void main(String[] args){
// RegistrationController rc = new RegistrationController();
// System.out.println(rc.validateUsername("dmartin"));
// System.out.println(rc.validateUsername("dmartin::"));
// System.out.println(rc.validateUsername("dmartin123"));
// }

  /**
   * Forgotten password.
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView forgottenPassword(HttpServletRequest request, HttpServletResponse response) throws Exception {

    if (!isFormSubmission(request)) {
      ModelAndView mav = new ModelAndView("forgottenPassword");
      return mav;
    }

    String email = request.getParameter("email");
    List<UserLogin> uls = ldapUtils.getUsernamePasswordForEmail(email);

    if (uls.isEmpty()) {
      ModelAndView mav = new ModelAndView("forgottenPassword");
      mav.addObject("email", email);
      mav.addObject("unrecognised", true);
      return mav;
    }

    for (UserLogin ul : uls) {

      // reset password
      String newPassword = generatePassword(ul.getUsername());
      ul.setPassword(newPassword);
      ldapUtils.updateUser(ul);

      // send verification email
      SimpleMailMessage verificationMessage = new SimpleMailMessage(userTemplateMessage);
      verificationMessage.setTo(email);
      String encryptedPassword = passwordUtils.encryptPassword(ul.getPassword(), true);
      verificationMessage.setSubject("Details for GBIF Data Portal");
      verificationMessage.setText("Please click here to login:\n\n" + "http://" + request.getHeader("host")
        + request.getContextPath() + "/register/" + "?u=" + ul.getUsername() + "&p=" + encryptedPassword
        + "\n\nUsername: " + ul.getUsername() + "\nPassword: " + ul.getPassword());
      try {
        mailSender.send(verificationMessage);
      } catch (MailException e) {
        // simply log it and go on...
        logger.error("Couldn't send message", e);
        ModelAndView mav = new ModelAndView("emailDetailsFailureView");
        mav.addObject("email", email);
        return mav;
      }
    }

    ModelAndView mav = new ModelAndView("emailDetailsSuccessView");
    mav.addObject("email", email);
    return mav;
  }

  /**
   * Generates a password for a reset password.
   * 
   * @param userName
   * @return
   */
  public String generatePassword(String userName) {
    String password = userName + passwordUtils.getEncryptKey() + System.currentTimeMillis();
    int hashCode = password.hashCode();
    return Integer.toHexString(hashCode);
  }


// public static void main(String[] args){
// try {
// String encrypted = encryptPassword("pass%^&�word__++1231:!!@�$", true);
// System.out.println("Enter encryption password:  "+encrypted);
// String decrypted = encryptPassword(encrypted, false);
// System.out.println("Enter decryption password:  "+decrypted);
// } catch (Exception e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// }


// public static String decryptPassword(String password) throws Exception{
// java.security.MessageDigest d =null;
// d = java.security.MessageDigest.getInstance("SHA-1");
// d.reset();
// d.update(password.getBytes());
// return PasswordUtils.byteArrayToHexString(d.digest());
// }

  /**
   * Searches through the parameters in the request to find the highest index of the given
   * property.
   * 
   * @see http://forum.springframework.org/showthread.php?t=19976
   * @param request The request
   * @param property The given property to search for
   * @return The maximum index for the given property, or -1 if the property is not indexed
   */
  protected final int getMaxIndex(HttpServletRequest request, String property) {
    int maxIndex = -1;
    for (Enumeration parameters = request.getParameterNames(); parameters.hasMoreElements();) {
      String parameterName = (String) parameters.nextElement();
      if (parameterName.startsWith(property)) {
        int startIndex = parameterName.indexOf("[");
        int endIndex = parameterName.indexOf("]");
        if ((startIndex == -1) || (endIndex == -1)) {
          logger.error("Non-indexed property - " + property);
          return (-1);
        }
        int index = Integer.parseInt(parameterName.substring((startIndex + 1), endIndex));
        if (index > maxIndex) {
          maxIndex = index;
        }
      }
    }
    return (maxIndex);
  }

  /**
   * @return Returns the resourceExtractionUtils.
   */
  public ResourceExtractionUtils getResourceExtractionUtils() {
    return resourceExtractionUtils;
  }

  /**
   * @param username
   * @return
   */
  private String getSystemUserName(String username) {
    if (StringUtils.isNotEmpty(usernamePostfix)) {
      return username + usernamePostfix;
    }
    return username;
  }

  /**
   * @see org.springframework.web.servlet.mvc.multiaction.MultiActionController#handleNoSuchRequestHandlingMethod(org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException,
   *      javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
   */
  @Override
  protected ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException exception,
    HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (StringUtils.isEmpty(exception.getMethodName())) {
      return viewAdminMenu(request, response);
    }
    return super.handleNoSuchRequestHandlingMethod(exception, request, response);
  }

  /**
   * Because the page can dynamically add more Secondary contacts, ensure that the contacts list in the command object
   * is of sufficient size to handle it.
   * 
   * @see org.springframework.web.servlet.mvc.BaseCommandController#initBinder(javax.servlet.http.HttpServletRequest,
   *      org.springframework.web.bind.ServletRequestDataBinder)
   */
  @Override
  protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
    if (binder.getTarget() instanceof ProviderDetail) {
      initProviderBinder(request, binder);
    } else if (binder.getTarget() instanceof ResourceDetail) {
      initResourceDetailBinder(request, binder);
    }
  }

  /**
   * Initialises the binder for a provider
   * This handles the resizing of the secondary contacts and resources
   */
  protected void initProviderBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
    ProviderDetail providerDetail = (ProviderDetail) binder.getTarget();
    logger.info("Checking to see if the contacts list needs padded");
    int maxSecondaryIndex = getMaxIndex(request, "businessSecondaryContacts");
    logger.info("maxSecondaryIndex: " + maxSecondaryIndex);
    int currentSize = providerDetail.getBusinessSecondaryContacts().size();
    logger.info("currentSize: " + currentSize);
    if (maxSecondaryIndex + 1 > currentSize) {
      for (int i = 0; i < (maxSecondaryIndex + 1 - currentSize); i++) {
        logger.info("creating a padded contact");
        providerDetail.getBusinessSecondaryContacts().add(providerDetail.new Contact());
      }
    } else if (maxSecondaryIndex + 1 < currentSize) {
      // shrink it if it's a submit - e.g contacts deleted
      if (isFormSubmission(request)) {
        logger.info("shrinking");
        providerDetail.setBusinessSecondaryContacts(providerDetail.getBusinessSecondaryContacts().subList(0,
          maxSecondaryIndex + 1));
      }
    }

    logger.info("Checking to see if the resource list needs padded");
    int maxResourceIndex = getMaxIndex(request, "businessResources");
    logger.info("maxResourceIndex: " + maxResourceIndex);
    currentSize = providerDetail.getBusinessResources().size();
    logger.info("currentSize: " + currentSize);
    if (maxResourceIndex + 1 > currentSize) {
      for (int i = 0; i < (maxResourceIndex + 1 - currentSize); i++) {
        logger.info("creating a padded resource");
        providerDetail.getBusinessResources().add(new ResourceDetail());
      }
    } else if (maxResourceIndex + 1 < currentSize) {
      // shrink it if it's a submit - e.g contacts deleted
      if (isFormSubmission(request)) {
        logger.info("shrinking");
        providerDetail.setBusinessResources(providerDetail.getBusinessResources().subList(0, maxResourceIndex + 1));
      }
    }
  }

  /**
   * Initialises the binder for a provider
   * This handles the resizing of the secondary contacts and resources
   */
  protected void initResourceDetailBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
    ResourceDetail resourceDetail = (ResourceDetail) binder.getTarget();
    String relatesToCountries = request.getParameter("relatesToCountries");
    if (StringUtils.isNotEmpty(relatesToCountries)) {
      ArrayList<String> relatesToCountriesList = new ArrayList<String>();
      StringTokenizer st = new StringTokenizer(relatesToCountries, ",");
      while (st.hasMoreTokens()) {
        String iso = st.nextToken();
        if (iso != null && iso.length() > 0)
          relatesToCountriesList.add(iso);
      }
      resourceDetail.setRelatesToCountries(relatesToCountriesList);
    }

    // check for N/A values
    Date startDate = DateUtil.getDateFromRequest(request, "startDate");
    Date endDate = DateUtil.getDateFromRequest(request, "endDate");
    resourceDetail.setStartDate(startDate);
    resourceDetail.setEndDate(endDate);

    // set N/A values to null
    if ("N/A".equals(resourceDetail.getRecordBasis()))
      resourceDetail.setRecordBasis(null);
    if ("N/A".equals(resourceDetail.getOwnerCountry()))
      resourceDetail.setOwnerCountry(null);
    if ("N/A".equals(resourceDetail.getIndexingStartTime()))
      resourceDetail.setIndexingStartTime(null);
    if ("N/A".equals(resourceDetail.getIndexingFrequency()))
      resourceDetail.setIndexingFrequency(null);
    if ("N/A".equals(resourceDetail.getIndexingMaxDuration()))
      resourceDetail.setIndexingMaxDuration(null);
  }

  /**
   * Determines if the request is a submission or not
   * 
   * @param request TO test
   * @return true if it is a POST, otherwise false
   */
  protected boolean isFormSubmission(HttpServletRequest request) {
    return "POST".equals(request.getMethod());
  }

  /**
   * AJAX entry point to dynamically call the provider with a metadata request
   */
  public ModelAndView issueMetadataRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ProviderDetail provider = retrieveProviderFromUDDI(request);
    Map<String, Object> data = new HashMap<String, Object>();
    if (StringUtils.isNotEmpty(request.getParameter(REQUEST_RESOURCE_URL))) {
      List<ResourceDetail> resources = contactProviderForMetadata(request.getParameter(REQUEST_RESOURCE_URL));
      replaceKnownResourcesWithUDDICopy(provider, resources);
      data.put(REQUEST_RESOURCES, resources);
    }
    data.putAll(referenceDataForResourceList(request));
    return new ModelAndView("registrationMetadataResourceList", data);
  }

  /**
   * Log the current user out.
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView logoutUser(HttpServletRequest request, HttpServletResponse response) throws Exception {
    request.getSession().invalidate();
    return new ModelAndView(new RedirectView(request.getContextPath() + "/register/"));
  }

  /**
   * Display a form for fully manual resource registration.
   */
  public ModelAndView manuallyRegisterResource(HttpServletRequest request, HttpServletResponse response)
    throws Exception {
    logger.debug("Entering the register data resources page");
    Map<String, Object> data = new HashMap<String, Object>();
    ProviderDetail provider = retrieveProviderFromUDDI(request);
    if (provider == null) {
      return new ModelAndView("registrationBadBusinessKey");
    }
    logger.debug("Provider has resource count: " + provider.getBusinessResources().size());
    ResourceDetail resourceDetail = new ResourceDetail();

    // set the resource type if passed as param
    String resourceType = request.getParameter("ResourceType");
    resourceDetail.setResourceType(resourceType);

    data.put(REQUEST_RESOURCE, resourceDetail);
    data.put("editableEndpoint", true);
    data.put(RegistrationController.REQUEST_PROVIDER_DETAIL, provider);
    data.putAll(referenceDataForResourceList(request));
    return new ModelAndView("registrationResourceManual", data);
  }

  /**
   * Create a new user in LDAP.
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView newUser(HttpServletRequest request, HttpServletResponse response) throws Exception {

    if (!isFormSubmission(request)) {
      ModelAndView mav = new ModelAndView("newUser");
      UserLogin ul = new UserLogin();
      mav.addObject("user", ul);
      return mav;
    }

    UserLogin ul = new UserLogin();
    ServletRequestDataBinder binder = createBinder(request, ul);
    binder.bind(request);
    BindingResult result = binder.getBindingResult();
    String systemUserName = getSystemUserName(ul.getUsername());
    String suggestedUsername = null;
    // validate
    if (StringUtils.isEmpty(ul.getUsername())) {
      result.rejectValue("username", ErrorMessageKeys.MUST_BE_SPECIFIED);
    } else if (ul.getUsername().length() < minimumUsernameLength) {
      result.rejectValue("username", ErrorMessageKeys.MUST_BE_MINIMIUM_LENGTH);
    } else if (!validateUsername(ul.getUsername())) {
      result.rejectValue("username", ErrorMessageKeys.CONTAINS_INVALID_CHARS);
    } else if (ldapUtils.userNameInUse(systemUserName)) {
      // iterate until a username available
      for (int i = 0; i < Integer.MAX_VALUE; i++) {
        suggestedUsername = getSystemUserName(ul.getUsername() + i);
        if (!ldapUtils.userNameInUse(suggestedUsername)) {
          break;
        }
      }
      result.rejectValue("username", ErrorMessageKeys.USERNAME_IN_USE);
    }

    if (StringUtils.isEmpty(ul.getFirstName())) {
      result.rejectValue("firstName", ErrorMessageKeys.MUST_BE_SPECIFIED);
    }
    if (StringUtils.isEmpty(ul.getSurname())) {
      result.rejectValue("surname", ErrorMessageKeys.MUST_BE_SPECIFIED);
    }

    if (!StringUtils.isEmpty(ul.getEmail())) {
      Pattern p = Pattern.compile(".+@.+\\.[a-z]+");
      Matcher m = p.matcher(ul.getEmail());
      boolean validEmail = m.matches();
      if (!validEmail)
        result.rejectValue("email", ErrorMessageKeys.INVALID_VALUE);
    } else {
      result.rejectValue("email", ErrorMessageKeys.MUST_BE_SPECIFIED);
    }
    if (StringUtils.isEmpty(ul.getPassword())) {
      result.rejectValue("password", ErrorMessageKeys.MUST_BE_SPECIFIED);
    } else if (!validatePassword(ul.getPassword())) {
      result.rejectValue("password", ErrorMessageKeys.MUST_BE_MINIMIUM_LENGTH);
    }

    if (result.hasErrors()) {
      ModelAndView mav = new ModelAndView("newUser");
      if (suggestedUsername != null) {
        mav.addObject("suggestedUsername", suggestedUsername);
      }
      mav.addObject(BindingResult.MODEL_KEY_PREFIX + "user", result);
      return mav;
    }

    // send verification email
    SimpleMailMessage verificationMessage = new SimpleMailMessage(userTemplateMessage);
    verificationMessage.setTo(ul.getEmail());
    String encryptedPassword = passwordUtils.encryptPassword(ul.getPassword(), true);
    verificationMessage.setSubject("Confirm e-mail address for GBIF Data Portal");
    verificationMessage.setText("Please visit the following link to confirm your e-mail address:\n\n" + "http://"
      + request.getHeader("host") + request.getContextPath() + "/user/verification" + "?fn=" + ul.getFirstName()
      + "&sn=" + ul.getSurname() + "&e=" + ul.getEmail() + "&u=" + ul.getUsername() + "&p=" + encryptedPassword);
    try {
      mailSender.send(verificationMessage);
    } catch (MailException e) {
      // simply log it and go on...
      logger.error("Couldn't send message", e);
      ModelAndView mav = new ModelAndView("registrationVerificationFailureView");
      mav.addObject("user", ul);
      return mav;
    }

    // successful
    ModelAndView mav = new ModelAndView("registrationVerificationSuccessView");
    mav.addObject("user", ul);
    return mav;
  }

  /**
   * Populates the ReferenceData for the provider page
   */
  protected Map<String, Object> referenceDataForProvider(HttpServletRequest request) throws Exception {
    Map<String, Object> data = new HashMap<String, Object>();
    data.put(RegistrationController.REQUEST_CONTACT_TYPES, ProviderDetail.ContactTypes.values());
    data.put(RegistrationController.REQUEST_COUNTRIES, geospatialManager.findAllCountries(request.getLocale())
      .getResults());
    return data;
  }


  /**
   * Populates the ReferenceData for the resource list page
   */
  protected Map<String, Object> referenceDataForResource(HttpServletRequest request, ResourceDetail resourceDetail)
    throws Exception {
    Map<String, Object> data = new HashMap<String, Object>();
    Locale locale = RequestContextUtils.getLocale(request);
    DataProviderDTO nubProvider = dataResourceManager.getNubDataProvider();
    DataResourceDTO dataResourceDTO = null;
    List<DataResourceDTO> resourceDTOs = dataResourceManager.getDataResourcesForProvider(nubProvider.getKey());
    if (resourceDTOs != null && !resourceDTOs.isEmpty())
      dataResourceDTO = resourceDTOs.get(0);

    data.put(RegistrationController.REQUEST_BASES_OF_RECORD, basisOfRecordTypes);
    data.put(RegistrationController.REQUEST_RESOURCE_NETWORKS, dataResourceManager.getResourceNetworkList());
    data.put(RegistrationController.REQUEST_COUNTRIES, geospatialManager.findAllCountries(locale).getResults());
    data.put(RegistrationController.REQUEST_DATA_PROVIDER, nubProvider);
    data.put(RegistrationController.REQUEST_DATA_RESOURCE, dataResourceDTO);
    data.put(RegistrationController.REQUEST_RESOURCE_TYPES, resourceTypes);
    data.put("messageSource", messageSource);

    if (StringUtils.isNotEmpty(resourceDetail.getHighestTaxaConceptId())) {
      data.put(RegistrationController.REQUEST_CONCEPTS,
        taxonomyManager.getClassificationFor(resourceDetail.getHighestTaxaConceptId(), true, null, true));
      data.put(RegistrationController.REQUEST_SELECTED_CONCEPT,
        taxonomyManager.getTaxonConceptFor(resourceDetail.getHighestTaxaConceptId()));
    } else {
      data.put(RegistrationController.REQUEST_CONCEPTS,
        taxonomyManager.getRootTaxonConceptsForTaxonomy(nubProvider.getKey(), dataResourceDTO.getKey()));
    }
    return data;
  }

  /**
   * Populates the ReferenceData for the resource list page
   */
  protected Map<String, Object> referenceDataForResourceList(HttpServletRequest request) throws Exception {
    return referenceDataForResourceList(request, null);
  }

  /**
   * Populates the ReferenceData for the resource list page
   */
  protected Map<String, Object> referenceDataForResourceList(HttpServletRequest request, ProviderDetail providerDetail)
    throws Exception {
    Map<String, Object> data = new HashMap<String, Object>();
    Locale locale = RequestContextUtils.getLocale(request);
    DataProviderDTO nubProvider = dataResourceManager.getNubDataProvider();
    DataResourceDTO dataResourceDTO = null;
    List<DataResourceDTO> resourceDTOs = dataResourceManager.getDataResourcesForProvider(nubProvider.getKey());
    if (resourceDTOs != null && !resourceDTOs.isEmpty())
      dataResourceDTO = resourceDTOs.get(0);

    data.put(RegistrationController.REQUEST_BASES_OF_RECORD, basisOfRecordTypes);
    data.put(RegistrationController.REQUEST_RESOURCE_NETWORKS, dataResourceManager.getResourceNetworkList());
    data.put(RegistrationController.REQUEST_COUNTRIES, geospatialManager.findAllCountries(locale).getResults());
    data.put(RegistrationController.REQUEST_DATA_PROVIDER, nubProvider);
    data.put(RegistrationController.REQUEST_DATA_RESOURCE, dataResourceDTO);
    data.put(RegistrationController.REQUEST_RESOURCE_TYPES, resourceTypes);
    data.put(RegistrationController.REQUEST_CONCEPTS,
      taxonomyManager.getRootTaxonConceptsForTaxonomy(nubProvider.getKey(), dataResourceDTO.getKey()));
    data.put("messageSource", messageSource);
    return data;
  }

  /**
   * Refreshes the details for the supplied dataset using the latest available for its metadata
   * request.
   * 
   * @return
   */
  public ModelAndView refreshMetadataDetails(HttpServletRequest request, HttpServletResponse response) throws Exception {

    // get the provider from UDDI if necessary
    ProviderDetail provider = retrieveProviderFromUDDI(request);
    if (provider == null) {
      return new ModelAndView("registrationBadBusinessKey");
    }

    // retrieve resource details
    ResourceDetail resource = retrieveDataResource(request, provider);

    // retrieve resources from access point
    List<ResourceDetail> resources = null;
    try {
      resources = contactProviderForMetadata(resource.getAccessPoint());
    } catch (Exception e) {
      logger.error(e.getMessage(), e);
      ModelAndView mav = new ModelAndView("refreshMetadataFailure");
      mav.addObject("resource", resource);
      return mav;
    }

    ResourceDetail metadataResource = null;
    if (resources.size() == 1) {
      logger.debug("Only one resource found at end point. Assuming same resource.");
      metadataResource = resources.get(0);
    } else {
      // use name to retrieve the properties
      for (ResourceDetail retrievedResource : resources) {
        if (resource.getName().equals(retrievedResource.getName())) {
          metadataResource = retrievedResource;
          break;
        }
      }
    }

    // store the updated properties
    List<String> updatedProperties = new ArrayList<String>();

    // find the list of metadata supplied properties
    List<String> readonlyProperties = null;

    boolean refreshFailure = false;

    // if not null, update properties available from metadata
    if (metadataResource != null) {
      readonlyProperties = retrieveReadonlyPropertiesForResource(metadataResource);
      for (String readonlyProperty : readonlyProperties) {
        Object newValue = PropertyUtils.getProperty(metadataResource, readonlyProperty);
        Object oldValue = PropertyUtils.getProperty(resource, readonlyProperty);
        if (newValue != null && !newValue.equals(oldValue)) {
          PropertyUtils.setProperty(resource, readonlyProperty, newValue);
          updatedProperties.add(readonlyProperty);
        }
      }
    } else {
      refreshFailure = true;
    }

    // uddiUtils.updateResource(resource, provider.getBusinessKey());

    ModelAndView mav = new ModelAndView("registrationResourceDetail");
    mav.addAllObjects(referenceDataForResource(request, resource));
    mav.addObject(RegistrationController.REQUEST_PROVIDER_DETAIL, provider);
    mav.addObject(RegistrationController.REQUEST_RESOURCE, resource);
    mav.addObject("readonlyProperties", readonlyProperties);
    mav.addObject("updatedProperties", updatedProperties);
    mav.addObject("refreshFailure", refreshFailure);
    mav.addObject("metadataRefresh", true);
    return mav;
  }

  /**
   * Setup page for a new data provider. When registering a new contact, some details are prepopulated from LDAP.
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView registerDataProvider(HttpServletRequest request, HttpServletResponse response) throws Exception {

    // get the authenticated user from LDAP
    UserLogin userLogin = ldapUtils.getUserLogin(request.getRemoteUser());

    // get the provider from UDDI if necessary
    ProviderDetail detail = new ProviderDetail();

    // these are prepopulated from LDAP
    detail.getBusinessPrimaryContact().setName(userLogin.getUsername());
    detail.getBusinessPrimaryContact().setPhone(userLogin.getTelephone());
    detail.getBusinessPrimaryContact().setEmail(userLogin.getEmail());

    Map<String, Object> data = new HashMap<String, Object>();
    data.putAll(referenceDataForProvider(request));
    data.put(RegistrationController.REQUEST_PROVIDER_DETAIL, detail);

    ModelAndView mav = new ModelAndView("registrationUpdateProviderDetail", data);
    return mav;
  }

  /**
   * Populates the page model for the resources within the provider given
   */
  public ModelAndView registerDataResources(HttpServletRequest request, HttpServletResponse response) throws Exception {

    logger.debug("Entering the register data resources page");
    Map<String, Object> data = new HashMap<String, Object>();
    ProviderDetail provider = retrieveProviderFromUDDI(request);
    if (provider == null) {
      return new ModelAndView("registrationBadBusinessKey");
    }

    List<ResourceDetail> resources = null;

    logger.debug("Provider has resource count: " + provider.getBusinessResources().size());
    data.put(RegistrationController.REQUEST_PROVIDER_DETAIL, provider);
    if (StringUtils.isNotEmpty(request.getParameter(REQUEST_RESOURCE_URL))) {
      // retrieve resource list from provider
      resources = contactProviderForMetadata(request.getParameter(REQUEST_RESOURCE_URL));
      replaceKnownResourcesWithUDDICopy(provider, resources);
      data.put(REQUEST_RESOURCES, resources);
    }

    ModelAndView mav = new ModelAndView("registrationResourceLookup", data);

    // add readonly settings
    if (resources != null && resources.size() > 0) {
      ResourceDetail rd = resources.get(0);
      String namespace = resourceType2namespaceList.get(rd.getResourceType());
      List<String> namespaces = new ArrayList<String>();
      namespaces.add(namespace);
      mav.addObject("readonlyProperties", resourceExtractionUtils.getMappedBeanPropertiesForNamespace(namespaces));
    }

    data.putAll(referenceDataForResourceList(request));
    mav.addObject(REQUEST_RESOURCE_TYPES, resourceTypes);
    mav.addAllObjects(data);
    return mav;
  }

  /**
   * Sets the guid on any resource that appears to be already in the provider
   * this uses the name only... should be enough... hmmm
   */
  protected void replaceKnownResourcesWithUDDICopy(ProviderDetail provider, List<ResourceDetail> resources) {

    // put all known resources into a map
    Map<String, ResourceDetail> resourcesByNames = new HashMap<String, ResourceDetail>();
    for (ResourceDetail resource : resources) {
      resourcesByNames.put(resource.getName(), resource);
    }

    // replace any retrieved from request with the UDDI copy
    for (ResourceDetail resource : provider.getBusinessResources()) {
      if (resourcesByNames.containsKey(resource.getName())) {

        ResourceDetail rd = resourcesByNames.get(resource.getName());
        int position = resources.indexOf(rd);
        if (position >= 0) {
          resources.remove(rd);
          // replace with UUDI version
          resources.add(position, resource);
        }
      }
    }
  }

  /**
   * Create a new user in LDAP.
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView resetPassword(HttpServletRequest request, HttpServletResponse response) throws Exception {

    if (!isFormSubmission(request)) {
      return new ModelAndView("resetPassword");
    }

    String password = request.getParameter("password");
    if (!validatePassword(password)) {
      ModelAndView mav = new ModelAndView("resetPassword");
      mav.addObject("password", password);
      mav.addObject("invalidPassword", true);
      return mav;
    }

    String remoteUser = request.getRemoteUser();
    ldapUtils.updatePassword(remoteUser, password);
    return new ModelAndView("passwordReset");
  }

  /**
   * Retrieve the data resource for the provided serviceKey
   * 
   * @param request
   * @param provider
   * @return
   */
  private ResourceDetail retrieveDataResource(HttpServletRequest request, ProviderDetail provider) {
    String serviceKey = request.getParameter("serviceKey");
    List<ResourceDetail> resources = provider.getBusinessResources();
    ResourceDetail resource = null;
    for (ResourceDetail res : resources) {
      if (res.getServiceKey().equals(serviceKey)) {
        resource = res;
        break;
      }
    }
    return resource;
  }

  /**
   * Retrieve the provider for this request from UDDI. Returns null in case of bad key.
   * 
   * @param request
   * @return
   * @throws NamingException
   * @throws UDDIException
   * @throws TransportException
   */
  private ProviderDetail retrieveProviderFromUDDI(HttpServletRequest request) {
    ProviderDetail provider = null;
    String key = request.getParameter(REQUEST_BUSINESS_UDDI_KEY);
    // get the authenticated user from LDAP
    try {
      // need to cache these LDAP details for speed
      UserLogin userLogin = ldapUtils.getUserLogin(request.getRemoteUser());
      if (StringUtils.isNotEmpty(key)) {
        provider = uddiUtils.createProviderFromUDDI(key, userLogin.getUsername());
      }
    } catch (Exception e) {
      logger.error(e.getMessage(), e);
    }
    return provider;
  }

  /**
   * Retrieves a list of properties that are readonly (i.e. available from metadata) for this resource.
   * 
   * @param resource
   * @return
   */
  private List<String> retrieveReadonlyPropertiesForResource(ResourceDetail resource) {
    if (resource.getResourceType() != null) {
      String namespace = resourceType2namespaceList.get(resource.getResourceType());
      List<String> namespaces = new ArrayList<String>();
      namespaces.add(namespace);
      return resourceExtractionUtils.getMappedBeanPropertiesForNamespace(namespaces);
    }
    return new ArrayList<String>();
  }

  /**
   * Populates the page model for the resources within the provider given
   */
  public ModelAndView retrieveRegisteredDataResources(HttpServletRequest request, HttpServletResponse response,
    ProviderDetail provider) throws Exception {
    logger.debug("Entering the resources page");
    Map<String, Object> data = new HashMap<String, Object>();
    if (logger.isDebugEnabled()) {
      logger.debug("Provider: " + provider);
      logger.debug("Provider has resource count: " + provider.getBusinessResources().size());
    }

    data.put(REQUEST_PROVIDER_DETAIL, provider);
    if (StringUtils.isNotEmpty(request.getParameter(REQUEST_RESOURCE_URL))) {
      // retrieve resource list from provider
      List<ResourceDetail> resources = contactProviderForMetadata(request.getParameter(REQUEST_RESOURCE_URL));
      replaceKnownResourcesWithUDDICopy(provider, resources);
      data.put(REQUEST_RESOURCES, resources);
    }
    data.putAll(referenceDataForResourceList(request, provider));
    return new ModelAndView("registrationResourceList", data);
  }

  /**
   * Admin task to review a users request to see details.
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView reviewUserRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

    ModelAndView mav = new ModelAndView("approveProviderListView");
    String user = request.getParameter("u");
    UserLogin ul = ldapUtils.getUserLogin(user);

    String[] requestedKeys = request.getParameterValues("r");
    List<String> requestedKeysList = new ArrayList<String>();

    for (String value : requestedKeys) {
      requestedKeysList.add(value);
    }

    // retrieve the providers this user currently has access to
    List<String> existingKeys = uddiUtils.getAssociatedBusinessKeys(user);

    // construct the list of providers to approve
    List<KeyValueDTO> pvs = uddiUtils.getProviderListAsKeyValues();
    List<KeyValueDTO> providersToApprove = new ArrayList<KeyValueDTO>();
    List<KeyValueDTO> existingLogins = new ArrayList<KeyValueDTO>();
    for (KeyValueDTO pv : pvs) {
      if (existingKeys.contains(pv.getKey()))
        existingLogins.add(pv);
      else if (requestedKeysList.contains(pv.getKey()))
        providersToApprove.add(pv);
    }

    mav.addObject("providerRegistrationLogins", existingLogins);
    mav.addObject("providersToApprove", providersToApprove);
    mav.addObject("user", ul);
    return mav;
  }

  /**
   * This will send an email for approval to portal@gbif.org to ask if someone can see a providers details
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView sendRegistrationLoginsRequest(HttpServletRequest request, HttpServletResponse response)
    throws Exception {

    String user = request.getParameter("user");
    if (StringUtils.isEmpty(user)) {
      user = request.getRemoteUser();
    }
    UserLogin ul = ldapUtils.getUserLogin(user);

    String[] businessKeysToRemove = request.getParameterValues("businessKeyToRemove");
    List<String> existingKeys = uddiUtils.getAssociatedBusinessKeys(user);

    // remove the selected registration logins
    if (businessKeysToRemove != null) {
      for (String element : businessKeysToRemove) {
        if (existingKeys.contains(element))
          uddiUtils.deleteRegistrationLogin(user, element);
      }
    }

    // send approval email
    String[] businessKeys = request.getParameterValues(REQUEST_BUSINESS_UDDI_KEY);

    // if not businesskeys selected, non requested return to menu
    if (businessKeys == null) {
      return new ModelAndView(new RedirectView(request.getContextPath() + "/register/"));
    }

    // send verification email
    SimpleMailMessage verificationMessage = new SimpleMailMessage(userTemplateMessage);
    verificationMessage.setTo(adminEmail);
    verificationMessage.setSubject("User has requested access to Provider Details");
    StringBuffer sb = new StringBuffer("Please click here to review request:\n\n");
    sb.append("http://");
    sb.append(request.getHeader("host"));
    sb.append(request.getContextPath());
    sb.append("/register/reviewUserRequest");
    sb.append("?u=" + user);
    for (String businessKey : businessKeys) {
      sb.append("&r=");
      sb.append(businessKey);
    }
    verificationMessage.setText(sb.toString());
    try {
      mailSender.send(verificationMessage);
    } catch (MailException e) {
      // simply log it and go on...
      logger.error("Couldn't send message", e);
      ModelAndView mav = new ModelAndView("emailDetailsFailureView");
      return mav;
    }

    // request sent view
    ModelAndView mav = new ModelAndView("requestSentView");
    mav.addObject("userLogin", ul);
    return mav;
  }

  /**
   * @param adminEmail the adminEmail to set
   */
  public void setAdminEmail(String adminEmail) {
    this.adminEmail = adminEmail;
  }

  /**
   * @param adminRole the adminRole to set
   */
  public void setAdminRole(String adminRole) {
    this.adminRole = adminRole;
  }

  /**
   * @param basisOfRecordTypes the basisOfRecordTypes to set
   */
  public void setBasisOfRecordTypes(List<String> basisOfRecordTypes) {
    this.basisOfRecordTypes = basisOfRecordTypes;
  }

  /**
   * @param dataResourceManager The dataResourceManager to set.
   */
  public void setDataResourceManager(DataResourceManager dataResourceManager) {
    this.dataResourceManager = dataResourceManager;
  }

  /**
   * @param geospatialManager The geospatialManager to set.
   */
  public void setGeospatialManager(GeospatialManager geospatialManager) {
    this.geospatialManager = geospatialManager;
  }

  /**
   * @param ldapUrl The ldapUrl to set.
   */
  public void setLdapUrl(String ldapUrl) {
    this.ldapUrl = ldapUrl;
  }

  /**
   * @param ldapUtils The ldapUtils to set.
   */
  public void setLdapUtils(LDAPUtils ldapUtils) {
    this.ldapUtils = ldapUtils;
  }

  /**
   * @param mailSender the mailSender to set
   */
  public void setMailSender(MailSender mailSender) {
    this.mailSender = mailSender;
  }

  /**
   * @param messageSource the messageSource to set
   */
  public void setMessageSource(MessageSource messageSource) {
    this.messageSource = messageSource;
  }

  /**
   * @param minimumPasswordLength the minimumPasswordLength to set
   */
  public void setMinimumPasswordLength(int minimumPasswordLength) {
    this.minimumPasswordLength = minimumPasswordLength;
  }


  /**
   * @param minimumUsernameLength the minimumUsernameLength to set
   */
  public void setMinimumUsernameLength(int minimumUsernameLength) {
    this.minimumUsernameLength = minimumUsernameLength;
  }

  /**
   * @param passwordUtils the passwordUtils to set
   */
  public void setPasswordUtils(PasswordUtils passwordUtils) {
    this.passwordUtils = passwordUtils;
  }

  /**
   * @param propertyStore the propertyStore to set
   */
  public void setPropertyStore(PropertyStore propertyStore) {
    this.propertyStore = propertyStore;
  }

  /**
   * @param resourceExtractionUtils The resourceExtractionUtils to set.
   */
  public void setResourceExtractionUtils(ResourceExtractionUtils resourceExtractionUtils) {
    this.resourceExtractionUtils = resourceExtractionUtils;
  }

  /**
   * @param resourceType2namespaceList the resourceType2namespaceList to set
   */
  public void setResourceType2namespaceList(Map<String, String> resourceType2namespaceList) {
    this.resourceType2namespaceList = resourceType2namespaceList;
  }

  /**
   * @param resourceTypes the resourceTypes to set
   */
  public void setResourceTypes(List<String> resourceTypes) {
    this.resourceTypes = resourceTypes;
  }

  /**
   * @param taxonomyManager The taxonomyManager to set.
   */
  public void setTaxonomyManager(TaxonomyManager taxonomyManager) {
    this.taxonomyManager = taxonomyManager;
  }

  /**
   * @param uddiUtils The uddiUtils to set.
   */
  public void setUddiUtils(UDDIUtils uddiUtils) {
    this.uddiUtils = uddiUtils;
  }

  /**
   * @param usernamePostfix the usernamePostfix to set
   */
  public void setUsernamePostfix(String usernamePostfix) {
    this.usernamePostfix = usernamePostfix;
  }

  /**
   * @param userTemplateMessage the userTemplateMessage to set
   */
  public void setUserTemplateMessage(SimpleMailMessage userTemplateMessage) {
    this.userTemplateMessage = userTemplateMessage;
  }

  /**
   * This is the entry point when the user types in the URL directly
   * It must have a key
   */
  public ModelAndView showDataResources(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // get the authenticated user from LDAP
    UserLogin userLogin = ldapUtils.getUserLogin(request.getRemoteUser());
    ProviderDetail detail = null;
    String key = request.getParameter(REQUEST_BUSINESS_UDDI_KEY);
    if (StringUtils.isNotEmpty(key)) {
      detail = uddiUtils.createProviderFromUDDI(key, userLogin.getUsername());
    }
    if (detail != null) {
      return retrieveRegisteredDataResources(request, response, detail);
    } else {
      logger.warn("Direct use of showDataResources with no key or invalid key: " + key);
      return new ModelAndView("registrationBadBusinessKey");
    }
  }

  /**
   * Saves the values for the data provider
   */
  @SuppressWarnings("unchecked")
  public ModelAndView synchroniseProvider(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ProviderDetail provider = new ProviderDetail();
    ServletRequestDataBinder binder = createBinder(request, provider);
    initBinder(request, binder);
    binder.bind(request);
    BindingResult result = binder.getBindingResult();
    validateDataProvider(provider, result);

    if (result.hasErrors()) {
      logger.info("Errors have occurred: " + result);
      Map<String, Object> data = new HashMap<String, Object>();
      // put the errors in the request
      data.put(BindingResult.MODEL_KEY_PREFIX + RegistrationController.REQUEST_PROVIDER_DETAIL, result);
      data.put(RegistrationController.REQUEST_CONTACT_TYPES, ProviderDetail.ContactTypes.values());
      data.put(RegistrationController.REQUEST_PROVIDER_DETAIL, result.getTarget());
      data.putAll(referenceDataForProvider(request));
      return new ModelAndView("registrationUpdateProviderDetail", data);
    } else {
      boolean success = synchroniseProvider(request, provider, result);
      if (!success) {
        return new ModelAndView("registrationUDDIFailure");
      }
      // return showDataResources(request, response, provider);
      return new ModelAndView(new RedirectView(request.getContextPath() + "/register/viewDataProvider?"
        + REQUEST_BUSINESS_UDDI_KEY + "=" + provider.getBusinessKey()));
    }
  }

  /**
   * Synchronises the provider with the LDAP help entry for it if it exists (UUID being set)
   * or creates a new one if it does not exist.
   * 
   * @param request For any extra parameters
   * @param provider To synchronise
   * @param errors To add to when the uddi cannot be sync'ed with
   */
  @SuppressWarnings("unchecked")
  protected boolean synchroniseProvider(HttpServletRequest request, ProviderDetail provider, Errors errors) {
    try {
      logger.debug("Synchronizing provider");
      boolean creatingNewProvider = StringUtils.isEmpty(provider.getBusinessKey());
      uddiUtils.synchroniseProvider(provider);
      if (creatingNewProvider) {
        uddiUtils.createRegistrationLogin(request.getRemoteUser(), provider.getBusinessKey());
      }
      return true;
    } catch (Exception e) {
      logger.error("Unexpected error communicating with UDDI: " + e.getMessage(), e);
      errors.reject(ErrorMessageKeys.UDDI_COMMUNICATION_ERROR);
    }
    return false;
  }

  /**
   * Populates the form for displaying the data provider if there is a key in the request. If there isn't then an
   * empty form is used, with a prepolution of the primary contact from LDAP
   * 
   * @TODO Exception handling for services down, invalid keys...
   */
  @SuppressWarnings("unchecked")
  public ModelAndView updateDataProvider(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // get the authenticated user from LDAP
    UserLogin userLogin = ldapUtils.getUserLogin(request.getRemoteUser());

    // get the provider from UDDI if necessary
    ProviderDetail detail = null;
    String key = request.getParameter(REQUEST_BUSINESS_UDDI_KEY);
    if (StringUtils.isNotEmpty(key)) {
      detail = uddiUtils.createProviderFromUDDI(key, userLogin.getUsername());
    }
    if (detail == null) {
      return new ModelAndView("registrationBadBusinessKey");
    }

    // LDAP details override the UDDI version - is this right?
// detail.getBusinessPrimaryContact().setName(namePhoneEmail[0]);
// detail.getBusinessPrimaryContact().setPhone(namePhoneEmail[1]);
// detail.getBusinessPrimaryContact().setEmail(namePhoneEmail[2]);

    Map<String, Object> data = new HashMap<String, Object>();
    data.putAll(referenceDataForProvider(request));
    data.put(RegistrationController.REQUEST_PROVIDER_DETAIL, detail);
    return new ModelAndView("registrationUpdateProviderDetail", data);
  }

  /**
   * This is the entry point for the AJAX call to update the resource
   */
  @SuppressWarnings("unchecked")
  public ModelAndView updateDataResource(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logger.info("Updating a resource...");
    debugRequestParams(request);
    ResourceDetail resource = new ResourceDetail();
    ServletRequestDataBinder binder = createBinder(request, resource);
    binder.bind(request);
    String businessKey = request.getParameter(REQUEST_BUSINESS_UDDI_KEY);
    BindingResult result = binder.getBindingResult();
    validateDataResource(resource, result);

    if (result.getErrorCount() == 0) {
      // it is always the last one, due to the fact that only one is ever submitted
      // although it's id may be something other than 0
      if (logger.isDebugEnabled()) {
        logger.debug("Resource name: " + resource.getName());
        logger.debug("Resource description: " + resource.getDescription());
      }
      uddiUtils.updateResource(resource, request.getParameter(REQUEST_BUSINESS_UDDI_KEY));
      return new ModelAndView(new RedirectView(request.getContextPath() + "/register/showDataResources?businessKey="
        + businessKey));
    } else {
      logger.error(result.getAllErrors());
      // put the errors in the request
      Map<String, Object> data = new HashMap<String, Object>();
      data.putAll(referenceDataForResource(request, resource));
      data.put(RegistrationController.REQUEST_RESOURCE, resource);
      data.put(BindingResult.MODEL_KEY_PREFIX + RegistrationController.REQUEST_RESOURCE, result);
      List<String> beanProperties = retrieveReadonlyPropertiesForResource(resource);
      data.putAll(referenceDataForResourceList(request));
      data.put("readonlyProperties", beanProperties);
      return new ModelAndView("registrationResourceDetail", data);
    }
  }

  /**
   * Update registration logins, creating those for the business keys passed in the request.
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView updateRegistrationLogins(HttpServletRequest request, HttpServletResponse response)
    throws Exception {

    String user = request.getParameter("user");
    boolean sendEmail = ServletRequestUtils.getBooleanParameter(request, "sendEmail", false);

    // if user is not supplied in a parameter, update is for the current user
    if (StringUtils.isEmpty(user)) {
      user = request.getRemoteUser();
    }

    String[] businessKeys = request.getParameterValues(REQUEST_BUSINESS_UDDI_KEY);
    String[] businessKeysToRemove = request.getParameterValues("businessKeyToRemove");
    List<String> existingKeys = uddiUtils.getAssociatedBusinessKeys(user);
    List<String> createdRegistrations = new ArrayList<String>();


    // add the selected registration logins
    if (businessKeys != null) {
      for (int i = 0; i < businessKeys.length; i++) {
        if (!existingKeys.contains(businessKeys[i])) {
          uddiUtils.createRegistrationLogin(user, businessKeys[i]);
          createdRegistrations.add(businessKeys[i]);
        }
      }
    }

    // if required sent a notification email
    if (sendEmail && !createdRegistrations.isEmpty()) {
      UserLogin userLogin = ldapUtils.getUserLogin(user);
      // send verification email
      SimpleMailMessage verificationMessage = new SimpleMailMessage(userTemplateMessage);
      verificationMessage.setTo(userLogin.getEmail());
      verificationMessage.setSubject("User has been granted access to Provider Details");
      StringBuffer sb =
        new StringBuffer("Your request to access the details of the following providers has been granted:\n\n");
      for (String createdRegistration : createdRegistrations) {
        ProviderDetail pd = uddiUtils.createProviderFromUDDI(createdRegistration, userLogin.getUsername());
        sb.append(pd.getBusinessName());
        sb.append("\n");
      }
      verificationMessage.setText(sb.toString());
      try {
        mailSender.send(verificationMessage);
      } catch (MailException e) {
        // simply log it and go on...
        logger.error("Couldn't send message", e);
      }
    }

    // remove the selected registration logins
    if (businessKeysToRemove != null) {
      for (String element : businessKeysToRemove) {
        if (existingKeys.contains(element))
          uddiUtils.deleteRegistrationLogin(user, element);
      }
    }
    return new ModelAndView(new RedirectView(request.getContextPath() + "/register/"));
  }

  /**
   * Show the update user screen.
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView userUpdate(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ModelAndView mav = new ModelAndView("registrationUserUpdate");
    return mav;
  }

  /**
   * Checks that Business details are all in there
   * 
   * @param command The form object
   * @param errors The errors to add to
   */
  protected void validateDataProvider(Object command, Errors errors) {
    ProviderDetail provider = (ProviderDetail) command;
    if (StringUtils.isEmpty(provider.getBusinessName())) {
      errors.rejectValue("businessName", ErrorMessageKeys.MUST_BE_SPECIFIED);
    }

// if (StringUtils.isEmpty(provider.getBusinessCountry())) {
// errors.rejectValue("businessCountry", ErrorMessageKeys.MUST_BE_SPECIFIED);
// }
    if (StringUtils.isEmpty(provider.getBusinessDescription())) {
      errors.rejectValue("businessDescription", ErrorMessageKeys.MUST_BE_SPECIFIED);
    }
    int secondaryContactCount = 0;
    for (ProviderDetail.Contact contact : provider.getBusinessSecondaryContacts()) {
      logger.info("Validating secondary contact");
      if (StringUtils.isEmpty(contact.getName())) {
        errors.rejectValue("businessSecondaryContacts[" + secondaryContactCount + "].name",
          ErrorMessageKeys.MUST_BE_SPECIFIED);
      }
      if (StringUtils.isEmpty(contact.getEmail())) {
        errors.rejectValue("businessSecondaryContacts[" + secondaryContactCount + "].email",
          ErrorMessageKeys.MUST_BE_SPECIFIED);
      }
      if (StringUtils.isEmpty(contact.getPhone())) {
        errors.rejectValue("businessSecondaryContacts[" + secondaryContactCount + "].phone",
          ErrorMessageKeys.MUST_BE_SPECIFIED);
      }
      secondaryContactCount++;
    }
    logger.info("Metadata validation error count: " + errors.getErrorCount());
  }

  /**
   * Validate this details of this data resource
   * 
   * @param resourceDetail
   * @param result
   */
  private void validateDataResource(ResourceDetail resourceDetail, BindingResult result) {
    if (StringUtils.isEmpty(resourceDetail.getName())) {
      result.rejectValue("name", ErrorMessageKeys.MUST_BE_SPECIFIED);
    }
    if (StringUtils.isEmpty(resourceDetail.getCode())) {
      result.rejectValue("code", ErrorMessageKeys.MUST_BE_SPECIFIED);
    }
    if (StringUtils.isEmpty(resourceDetail.getAccessPoint())) {
      result.rejectValue("accessPoint", ErrorMessageKeys.MUST_BE_SPECIFIED);
    }

    if (resourceDetail.getNorthCoordinate() != null) {
      if (resourceDetail.getNorthCoordinate() > 90 || resourceDetail.getNorthCoordinate() < -90)
        result.rejectValue("northCoordinate", ErrorMessageKeys.INVALID_LATITUDE);
    }
    if (resourceDetail.getSouthCoordinate() != null) {
      if (resourceDetail.getSouthCoordinate() > 90 || resourceDetail.getSouthCoordinate() < -90)
        result.rejectValue("southCoordinate", ErrorMessageKeys.INVALID_LATITUDE);
    }
    if (resourceDetail.getEastCoordinate() != null) {
      if (resourceDetail.getEastCoordinate() > 180 || resourceDetail.getEastCoordinate() < -180)
        result.rejectValue("eastCoordinate", ErrorMessageKeys.INVALID_LONGITUDE);
    }
    if (resourceDetail.getWestCoordinate() != null) {
      if (resourceDetail.getWestCoordinate() > 180 || resourceDetail.getWestCoordinate() < -180)
        result.rejectValue("westCoordinate", ErrorMessageKeys.INVALID_LONGITUDE);
    }

    if (resourceDetail.getWestCoordinate() != null && resourceDetail.getEastCoordinate() != null
      && resourceDetail.getWestCoordinate() > resourceDetail.getEastCoordinate()) {
      result.rejectValue("westCoordinate", ErrorMessageKeys.WEST_GREATER_THAN_EAST);
    }
    if (resourceDetail.getNorthCoordinate() != null && resourceDetail.getSouthCoordinate() != null
      && resourceDetail.getSouthCoordinate() > resourceDetail.getNorthCoordinate()) {
      result.rejectValue("southCoordinate", ErrorMessageKeys.SOUTH_GREATER_THAN_NORTH);
    }
  }

  /**
   * @param password
   * @return
   */
  private boolean validatePassword(String password) {
    if (password != null && password.length() > minimumPasswordLength)
      return true;
    return false;
  }

  /**
   * Check for invalid characters.
   * 
   * @param username
   * @return
   */
  private boolean validateUsername(String username) {
    if (username != null && username.length() > minimumUsernameLength) {
      Pattern p = Pattern.compile("^[A-Za-z]\\w{5,30}$");
      Matcher m = p.matcher(username);
      boolean valid = m.matches();
      return valid;
    }
    return false;
  }

  /**
   * User verification.
   * 
   * @param request
   * @param response
   * @return
   * @throws Exception
   */
  public ModelAndView verification(HttpServletRequest request, HttpServletResponse response) throws Exception {
    String firstName = request.getParameter("fn");
    String surname = request.getParameter("sn");
    String email = request.getParameter("e");
    String username = request.getParameter("u");
    String password = request.getParameter("p");
    String decryptedPassword = passwordUtils.encryptPassword(password, false);
    UserLogin userLogin = new UserLogin(firstName, surname, email, username, decryptedPassword);
    // check the user hasn't already been created
    if (!ldapUtils.userNameInUse(username)) {
      ldapUtils.createNewUser(userLogin);
      List<DataProviderDTO> dataProviders = dataResourceManager.findDataProvidersForUser(userLogin.getEmail());
      for (DataProviderDTO dataProvider : dataProviders) {
        uddiUtils.createRegistrationLogin(username, dataProvider.getUuid());
      }
    }
    return new ModelAndView(new RedirectView(request.getContextPath() + "/register/?u=" + username + "&p=" + password));
  }

  /**
   * The entry point once a user has logged in.
   */
  @SuppressWarnings("unchecked")
  public ModelAndView viewAdminMenu(HttpServletRequest request, HttpServletResponse response) throws Exception {

    UserLogin userLogin = ldapUtils.getUserLogin(request.getRemoteUser());
    // retrieve login/business key mapping

    List<String> businessKeys = uddiUtils.getAssociatedBusinessKeys(request.getRemoteUser());
    List<ProviderDetail> pds = new ArrayList<ProviderDetail>();

    for (int i = 0; i < businessKeys.size(); i++) {
      ProviderDetail pd = uddiUtils.createProviderFromUDDI(businessKeys.get(i), userLogin.getFullName());
      pds.add(pd);
    }
    ModelAndView mav = new ModelAndView("registrationMain");
    mav.addObject("providerDetails", pds);

    if (StringUtils.isNotEmpty(userLogin.getFullName())) {
      mav.addObject("username", userLogin.getFullName());
    } else {
      mav.addObject("username", userLogin.getUsername());
    }
    return mav;
  }

  /**
   * Populates the form for displaying the data provider if there is a key in the request. If there isn't then an
   * empty form is used, with a prepolution of the primary contact from LDAP
   * 
   * @TODO Exception handling for services down, invalid keys...
   */
  @SuppressWarnings("unchecked")
  public ModelAndView viewDataProvider(HttpServletRequest request, HttpServletResponse response) throws Exception {

    // get the provider from UDDI if necessary
    ProviderDetail provider = retrieveProviderFromUDDI(request);
    if (provider == null) {
      return new ModelAndView("registrationBadBusinessKey");
    }

    Map<String, Object> data = new HashMap<String, Object>();
    data.putAll(referenceDataForProvider(request));
    data.put(RegistrationController.REQUEST_PROVIDER_DETAIL, provider);
    return new ModelAndView("registrationProviderDetail", data);
  }

  /**
   * Populates the form for displaying the data provider if there is a key in the request. If there isn't then an
   * empty form is used, with a prepolution of the primary contact from LDAP
   * 
   * @TODO Exception handling for services down, invalid keys...
   */
  @SuppressWarnings("unchecked")
  public ModelAndView viewDataResource(HttpServletRequest request, HttpServletResponse response) throws Exception {

    // get the provider from UDDI if necessary
    ProviderDetail provider = retrieveProviderFromUDDI(request);
    if (provider == null) {
      return new ModelAndView("registrationBadBusinessKey");
    }

    // retrieve resource details
    ResourceDetail resource = retrieveDataResource(request, provider);

    if (resource == null) {
      return new ModelAndView("registrationBadBusinessKey");
    }

    Map<String, Object> data = new HashMap<String, Object>();
    data.putAll(referenceDataForResource(request, resource));
    data.put(RegistrationController.REQUEST_PROVIDER_DETAIL, provider);
    data.put(RegistrationController.REQUEST_RESOURCE, resource);

    List<String> beanProperties = retrieveReadonlyPropertiesForResource(resource);
    data.put("readonlyProperties", beanProperties);
    return new ModelAndView("registrationResourceDetail", data);
  }
}